using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Windows.Forms.VisualStyles;

namespace VSSDKAssist.Recipes.Common.Editors.Grid
{
    /// <summary>
    /// Custom column for Wizard datagrids
    /// </summary>
    [DesignerCategory("Code")]
    public class DesignerColumn : DataGridViewColumn
    {
        private PropertyDescriptor propertyDescriptor;
        private string typeEditor;
        private string typeConverter;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:DesignerColumn"/> class.
        /// </summary>
        public DesignerColumn()
            : base(new DesignerCell())
        {
        }

        protected DesignerColumn(DataGridViewCell cellTemplate)
            : base(cellTemplate)
        {
        }

        private DesignerCell TypedCell
        {
            get { return (DesignerCell)this.CellTemplate; }
        }

        /// <summary>
        /// Gets or sets the type editor.
        /// </summary>
        /// <value>The type editor.</value>
        [DefaultValue(null)]
        [Browsable(true)]
        [Description("The UI type editor")]
        [Category("Design")]
        public virtual string TypeEditor
        {
            get { return typeEditor; }
            set { typeEditor = value; }
        }

        /// <summary>
        /// Gets or sets the type converter.
        /// </summary>
        /// <value>The type converter.</value>
        [DefaultValue(null)]
        [Browsable(true)]
        [Category("Design")]
        [Description("The converter for the value type")]
        public string TypeConverter
        {
            get { return typeConverter; }
            set { typeConverter = value; }
        }

        /// <summary>
        /// Gets or sets the property descriptor.
        /// </summary>
        /// <value>The property descriptor.</value>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public PropertyDescriptor PropertyDescriptor
        {
            get { return propertyDescriptor; }
            set { propertyDescriptor = value; }
        }

        /// <summary>
        /// Gets the bound property editor.
        /// </summary>
        /// <returns></returns>
        private UITypeEditor GetBoundPropertyEditor()
        {
            PropertyDescriptor property = GetBoundProperty();
            string editorName = TypeEditor;
            if(String.IsNullOrEmpty(editorName) && property != null)
            {
                EditorAttribute editorAttr = (EditorAttribute)property.Attributes[typeof(EditorAttribute)];
                if(editorAttr != null && !String.IsNullOrEmpty(editorAttr.EditorTypeName))
                {
                    editorName = editorAttr.EditorTypeName;
                }
            }

            if(!String.IsNullOrEmpty(editorName))
            {
                return (UITypeEditor)Activator.CreateInstance(LoadType(editorName));
            }
            else
            {
                return (UITypeEditor)TypeDescriptor.GetEditor(ValueType, typeof(UITypeEditor));
            }
        }

        /// <summary>
        /// Gets the bound property converter.
        /// </summary>
        /// <returns></returns>
        private TypeConverter GetBoundPropertyConverter()
        {
            PropertyDescriptor property = GetBoundProperty();
            TypeConverter converterInstance;
            string converterName = TypeConverter;
            if(String.IsNullOrEmpty(converterName) && property != null)
            {
                TypeConverterAttribute converterAttr = (TypeConverterAttribute)property.Attributes[typeof(TypeConverterAttribute)];
                if(converterAttr != null && !String.IsNullOrEmpty(converterAttr.ConverterTypeName))
                {
                    converterName = converterAttr.ConverterTypeName;
                }
            }

            if(!String.IsNullOrEmpty(converterName))
            {
                Type converterType = LoadType(converterName);
                converterInstance = (TypeConverter)Activator.CreateInstance(converterType);
            }
            else
            {
                converterInstance = TypeDescriptor.GetConverter(ValueType);
            }

            return converterInstance;
        }

        /// <summary>
        /// Loads the type.
        /// </summary>
        /// <param name="typeName">Name of the type.</param>
        /// <returns></returns>
        private Type LoadType(string typeName)
        {
            if(DataGridView.Site != null)
            {
                ITypeResolutionService typeResolution = (ITypeResolutionService)DataGridView.Site.GetService(typeof(ITypeResolutionService));
                if(typeResolution != null)
                {
                    return typeResolution.GetType(typeName, true);
                }
            }

            return Type.GetType(typeName, true);
        }

        /// <summary>
        /// Gets the bound property.
        /// </summary>
        /// <returns></returns>
        protected PropertyDescriptor GetBoundProperty()
        {
            if(propertyDescriptor != null)
            {
                return propertyDescriptor;
            }

            if(DataGridView.DataSource == null)
            {
                return null;
            }

            BindingMemberInfo binding = new BindingMemberInfo(DataPropertyName);
            CurrencyManager dataManager = DataGridView.BindingContext[DataGridView.DataSource, binding.BindingPath] as CurrencyManager;
            if(dataManager == null)
            {
                return null;
            }

            PropertyDescriptor property = dataManager.GetItemProperties().Find(binding.BindingField, true);
            if(property == null)
            {
                throw new ArgumentException(DataPropertyName);
            }

            return property;
        }

        public class DesignerCell : DataGridViewTextBoxCell
        {
            protected DesignerColumn OwningDesignerColumn
            {
                get { return (DesignerColumn)OwningColumn; }
            }

            /// <summary>
            /// Gets the value of the cell as formatted for display.
            /// </summary>
            /// <param name="value">The value to be formatted.</param>
            /// <param name="rowIndex">The index of the cell's parent row.</param>
            /// <param name="cellStyle">The <see cref="T:System.Windows.Forms.DataGridViewCellStyle"></see> in effect for the cell.</param>
            /// <param name="valueTypeConverter">A <see cref="T:System.ComponentModel.TypeConverter"></see> associated with the value type that provides custom conversion to the formatted value type, or null if no such custom conversion is needed.</param>
            /// <param name="formattedValueTypeConverter">A <see cref="T:System.ComponentModel.TypeConverter"></see> associated with the formatted value type that provides custom conversion from the value type, or null if no such custom conversion is needed.</param>
            /// <param name="context">A bitwise combination of <see cref="T:System.Windows.Forms.DataGridViewDataErrorContexts"></see> values describing the context in which the formatted value is needed.</param>
            /// <returns>
            /// The formatted value of the cell or null if the cell does not belong to a <see cref="T:System.Windows.Forms.DataGridView"></see> control.
            /// </returns>
            /// <exception cref="T:System.Exception">Formatting failed and either there is no handler for the <see cref="E:System.Windows.Forms.DataGridView.DataError"></see> event of the <see cref="T:System.Windows.Forms.DataGridView"></see> control or the handler set the <see cref="P:System.Windows.Forms.DataGridViewDataErrorEventArgs.ThrowException"></see> property to true. The exception object can typically be cast to type <see cref="T:System.FormatException"></see>.</exception>
            protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
            {
                return base.GetFormattedValue(
                    ParseFormattedValue(value, cellStyle, formattedValueTypeConverter, valueTypeConverter),
                    rowIndex, ref cellStyle,
                    valueTypeConverter ?? OwningDesignerColumn.GetBoundPropertyConverter(),
                    formattedValueTypeConverter ?? OwningDesignerColumn.GetBoundPropertyConverter(),
                    context);
            }

            /// <summary>
            /// Converts a value formatted for display to an actual cell value.
            /// </summary>
            /// <param name="formattedValue">The display value of the cell.</param>
            /// <param name="cellStyle">The <see cref="T:System.Windows.Forms.DataGridViewCellStyle"></see> in effect for the cell.</param>
            /// <param name="formattedValueTypeConverter">A <see cref="T:System.ComponentModel.TypeConverter"></see> for the display value type, or null to use the default converter.</param>
            /// <param name="valueTypeConverter">A <see cref="T:System.ComponentModel.TypeConverter"></see> for the cell value type, or null to use the default converter.</param>
            /// <returns>The cell value.</returns>
            /// <exception cref="T:System.FormatException">The <see cref="P:System.Windows.Forms.DataGridViewCell.FormattedValueType"></see> property value is null.-or-The <see cref="P:System.Windows.Forms.DataGridViewCell.ValueType"></see> property value is null.-or-formattedValue cannot be converted.</exception>
            /// <exception cref="T:System.ArgumentNullException">cellStyle is null.</exception>
            /// <exception cref="T:System.ArgumentException">formattedValue is null.-or-The type of formattedValue does not match the type indicated by the <see cref="P:System.Windows.Forms.DataGridViewCell.FormattedValueType"></see> property. </exception>
            public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter)
            {
                if(formattedValue != null)
                {
                    if(this.ValueType.IsAssignableFrom(formattedValue.GetType()))
                    {
                        return formattedValue;
                    }
                    else
                    {
                        TypeConverter converter = OwningDesignerColumn.GetBoundPropertyConverter();
                        if(converter.CanConvertFrom(formattedValue.GetType()) &&
                            converter.IsValid(formattedValue))
                        {
                            return converter.ConvertFrom(formattedValue);
                        }
                    }
                }

                return null;
            }

            /// <summary>
            /// Gets the type of the formatted value associated with the cell.
            /// </summary>
            /// <value></value>
            /// <returns>A <see cref="T:System.Type"></see> representing the <see cref="T:System.String"></see> type in all cases.</returns>
            public override Type FormattedValueType
            {
                get
                {
                    return typeof(string);
                }
            }

            private bool HasStandardValues()
            {
                TypeConverter converter = OwningDesignerColumn.GetBoundPropertyConverter();
                return converter != null &&
                       converter.GetStandardValuesSupported();
            }

            protected override void OnMouseDown(DataGridViewCellMouseEventArgs e)
            {
                base.OnMouseDown(e);
                Point cellLocation = DataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Location;
                cellLocation.Offset(e.Location);
                if(buttonBounds.Contains(cellLocation))
                {
                    pressed = true;
                }
            }

            protected override void OnMouseUp(DataGridViewCellMouseEventArgs e)
            {
                base.OnMouseUp(e);
                pressed = false;
            }

            bool pressed = false;

            protected override void OnMouseClick(DataGridViewCellMouseEventArgs e)
            {
                Point cellLocation = DataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Location;
                cellLocation.Offset(e.Location);
                if(buttonBounds.Contains(cellLocation))
                {
                    UITypeEditor editor = OwningDesignerColumn.GetBoundPropertyEditor();
                    if(editor != null && editor.GetEditStyle() == UITypeEditorEditStyle.Modal)
                    {
                        ITypeDescriptorContext context = new TypeDescriptorContext(DataGridView.Site, OwningDesignerColumn.GetBoundProperty(), this);
                        object value = GetValue(e.RowIndex);
                        value = editor.EditValue(context, DataGridView.Site, value);
                        SetValue(e.RowIndex, value);
                    }

                    // Notify the grid the cell was modified
                    DataGridView.NotifyCurrentCellDirty(true);
                    DataGridView.NotifyCurrentCellDirty(false);
                }
                else
                {
                    base.OnMouseClick(e);
                }
            }

            private Rectangle buttonBounds = Rectangle.Empty;

            /// <summary>
            /// Moves the error icon to the left when the button is shown
            /// </summary>
            protected override Rectangle GetErrorIconBounds(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex)
            {
                Rectangle iconBounds = base.GetErrorIconBounds(graphics, cellStyle, rowIndex);

                if((State & DataGridViewElementStates.Selected) > 0)
                {
                    iconBounds.Offset(-GetSize(rowIndex).Height, 0);
                }

                return iconBounds;
            }

            /// <summary>
            /// Paints the dots button when the cell is selected
            /// </summary>
            protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex,
                DataGridViewElementStates cellState, object value, object formattedValue, string errorText,
                DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
            {
                base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);

                if((cellState & DataGridViewElementStates.Selected) > 0)
                {
                    Rectangle buttonRect = new Rectangle(cellBounds.Right - cellBounds.Height, cellBounds.Top, cellBounds.Height - 1, cellBounds.Height - 1);
                    ButtonRenderer.DrawButton(graphics, buttonRect, "...", cellStyle.Font, false, pressed ? PushButtonState.Pressed : PushButtonState.Default);
                    buttonBounds = buttonRect;
                }
            }

            #region Locally used TypeDescriptorContext implementation

            private class TypeDescriptorContext : ITypeDescriptorContext, IWindowsFormsEditorService
            {
                private IServiceProvider serviceProvider;
                private PropertyDescriptor propertyDescriptor;
                private object instance;

                public TypeDescriptorContext(IServiceProvider serviceProvider, PropertyDescriptor propertyDescriptor, object instance)
                {
                    this.serviceProvider = serviceProvider;
                    this.propertyDescriptor = propertyDescriptor;
                    this.instance = instance;
                }

                public IContainer Container
                {
                    get { throw new NotImplementedException(); }
                }

                public object Instance
                {
                    get { return instance; }
                }

                public void OnComponentChanged()
                {
                    throw new NotImplementedException();
                }

                public bool OnComponentChanging()
                {
                    throw new NotImplementedException();
                }

                public PropertyDescriptor PropertyDescriptor
                {
                    get { return propertyDescriptor; }
                }

                public object GetService(Type serviceType)
                {
                    if(serviceType == typeof(IWindowsFormsEditorService))
                    {
                        return this;
                    }
                    return serviceProvider.GetService(serviceType);
                }

                #region IWindowsFormsEditorService Members

                void IWindowsFormsEditorService.CloseDropDown()
                {
                    throw new NotImplementedException();
                }

                void IWindowsFormsEditorService.DropDownControl(Control control)
                {
                    throw new NotImplementedException();
                }

                DialogResult IWindowsFormsEditorService.ShowDialog(Form dialog)
                {
                    IUIService uiService = (IUIService)GetService(typeof(IUIService));
                    if(uiService != null)
                    {
                        return uiService.ShowDialog(dialog);
                    }
                    return dialog.ShowDialog();
                }

                #endregion
            }

            #endregion
        }
    }
}
